package unifor.mia.redes.relprotocol;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Scanner;

import unifor.mia.redes.relprotocol.channel.Channel;
import unifor.mia.redes.relprotocol.msg.ACK;
import unifor.mia.redes.relprotocol.msg.Mensagem;

public class ReliableSender extends ReliableProtocolComponent {

	private int N = 5; // tamanho da janela 
	private ArrayDeque<Mensagem> pacotesNaoReconhecidos = new ArrayDeque<Mensagem>();

	private int  timeout = 500;
	private long time = 0l;
	private boolean timerOn = false;
	
	@Override
	public void run() {
		String mensagem = "";
		Scanner input = new Scanner(System.in);
		
		while(true){
			System.out.print(">");
			mensagem = input.nextLine();
			send(mensagem);
		}

	}
	
	public void send(String texto) {
		if (texto == null){
			texto = "";
		}

		ArrayList<Mensagem> bloco = empacota(texto);

		ACK confirmation = null;

		int i=0;
		while(i < bloco.size() || !pacotesNaoReconhecidos.isEmpty()) {
//			System.out.println("[Sender] Tamanho do buffer de pacotes nao reconhecidos = " + pacotesNaoReconhecidos.size());
			if(i < bloco.size()){
				Mensagem mensagem = bloco.get(i);
				if(pacotesNaoReconhecidos.size() < N) {
					Channel.write(mensagem);
					if (pacotesNaoReconhecidos.size() == 0) {
						startTimer();
					}
					pacotesNaoReconhecidos.add(mensagem);
					i++;
//				} else {
//					System.out.println("[Sender] Janela cheia. Aguardando um confirmação do destinatario...");
				}
				if(i > bloco.size()){
					System.out.println("[Sender] Envio de mensagens finalizado. Falta receber "+ pacotesNaoReconhecidos.size() + " ACKs");
				}
			}
//			System.out.println("[Sender] Tamanho do buffer de pacotes nao reconhecidos = " + pacotesNaoReconhecidos.size());
			confirmation = Channel.getConfirmation();
			if (confirmation != null && confirmation.getMessage().equals("ACK")) {
				System.out.println("[Sender] " + confirmation.getMessage() + " com SEQ = " + confirmation.getSeq() + " recebido no Sender");
				confirmar(confirmation.getSeq());
//			} else if(confirmation != null) {
//				System.out.println("[Sender] " + confirmation.getMessage() + " com SEQ = " + confirmation.getSeq() + " recebido no Sender e DESCONSIDERADO");
			}
//			System.out.println("[Sender] Tamanho do buffer de pacotes nao reconhecidos = " + pacotesNaoReconhecidos.size());
			checkTimeout();
			
		}
	}

	private void printMensagensNaoConfirmadas() {
		Object[] msgs = pacotesNaoReconhecidos.toArray();
		
		String txt = "";
		for (int i = 0; i < msgs.length; i++) {
			txt = txt+ ((Mensagem)msgs[i]).getSeq() + ", ";
		}
		System.out.println("Mensagens ainda sem confirmação: " + txt);
	}

	private void startTimer() {
		timerOn = true;
		time = System.currentTimeMillis();
	}

	private void stopTimer() {
		timerOn = false;
	}

	private void checkTimeout() {
		long now = System.currentTimeMillis();
		if (timerOn && (now - time) >= timeout) {
			System.out.println("[Sender] TIMEOUT! Reenviando " + pacotesNaoReconhecidos.size() + " pacotes nao reconhecidos até agora");
			printMensagensNaoConfirmadas();
			timeout();
		}
	}

	private void timeout() {
		Object[] msgs = pacotesNaoReconhecidos.toArray();
		
		for (int i = 0; i < msgs.length; i++) {
			System.out.println("[Sender] Enviando backlog");
			Channel.write((Mensagem)msgs[i]);
		}
		startTimer();
	}

	private void confirmar(long sequencial) {
		if (sequencial >= 0) {
			Mensagem m = pacotesNaoReconhecidos.peek();
			while(m != null && m.getSeq() <= sequencial) {
				m = pacotesNaoReconhecidos.poll();
				m = pacotesNaoReconhecidos.peek();
			}
			if (pacotesNaoReconhecidos.size() == 0) {
				stopTimer();
			} else {
				startTimer();
			}
		}
	}

	private ArrayList<Mensagem> empacota(String mensagem) {
		
		ArrayList<Mensagem> bloco = new ArrayList<Mensagem>();
		
		int j = 1;
		int ultInd = 0;
		
		if(mensagem == null || mensagem.equals("")){
			 bloco.add(criaMensagem(""));
		} else {
			for(int i=0; i < mensagem.length(); i++){
				if(j == TAMANHO_PACOTE || i == mensagem.length() - 1){
					Mensagem msg = criaMensagem(mensagem.substring(ultInd, i+1));
					bloco.add(msg);
					ultInd = i+1;
					j=1;
				} else {
					j++;
				}
			}
		}		
		bloco.get(0).setStarting();
		bloco.get(bloco.size() - 1).setEnding();
		return bloco;
	}

}
